import csv
import os
import random
import threading
import time
import urllib
from concurrent.futures.thread import ThreadPoolExecutor
from pathlib import Path
import tkinter
import tkinter as tk
import tkinter.ttk
from tkinter import *
from tkinter import ttk, messagebox
from tkinter.messagebox import showinfo
from turtle import Canvas
import re
import requests
from PIL import ImageTk
from atm import download
from bs4 import BeautifulSoup

file_path = "..\\程序执行结果文件\\"
network_interval = 0.1  # 联网间隔，自动调整避免503
flage = 0
start1 = 30
start2 = 120
start3 = 210
start4 = 300
count = 0


# 爬取url的内容
def open_url(url):
    global network_interval
    header = [
        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
        'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
        'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)'
    ]  # 设置多个use-agent存放在header列表中，用于伪装爬虫身份
    header = {'User-Agent': str(header[random.randint(0, 3)])}  # 每次随机一个use-agent存放到header中
    req = requests.get(url=url, headers=header)  # 向服务器发起请求获取数据，返回值是response类型
    if req.status_code != 200:  # 如果发现响应的状态码不等于200，请求失败
        network_interval += 0.1  # 设置网络间隔时间自增加0.1，让下次读取章节暂停时间增加
        print('（503 正在重试...）', round(network_interval, 2))  # 提示重试
        return open_url(url)  # 请求失败递归重新执行open_url()
    if network_interval > 0.1:  # 如果网络间隔时间大于0.1
        network_interval *= 0.995  # 表示之前失败过，此次重新调整网络时间间隔，减少时间间隔
    response = req.content  # 获取服务器响应的内容，二进制类型
    response_decode = response.decode("UTF-8", "ignore")  # 将获取的内容转换为UTF-8编码格式，是该网站的编码格式
    return response_decode  # 返回UTF-8格式的内容


# 获取首页各类小说URL
def get_novelTypelUrl(indexUrl):
    html = open_url(indexUrl)  # 调用open_url()函数传入url获取首页内容
    soup = BeautifulSoup(html, "html.parser")  # 解析网页
    urlList = []
    textList = []
    aList = []
    for novels in soup.select('.nav'):  # select出class=nav的div元素
        aList += novels.find_all(re.compile('a'))  # 正则选出div内的a标签列表拼接到aList列表存取
    for novels in aList:  # 遍历存取a标签的列表
        text = novels.text  # 获取a标签里的内容
        textList.append(text)  # 将找到的a标签内容添加到textList列表中存取
        novels = novels['href']  # 提取a标签内的链接href
        url = indexUrl + novels  # 首页网址拼接上后面的其他类型小说网址（这部分网址只是一部分，需要拼接上首页url）
        urlList.append(url)  # 将拼接后的网址存取到列表中
    textList = textList[2:8]  # 切割掉与小说无关的文本
    urlList = urlList[2:8]  # 切割掉与小说无关的url
    dict = {}
    for i in range(len(urlList)):  # 循环将文本列表内元素和url列表元素用字典一一对应存储
        dict[textList[i]] = urlList[i]  # key值为第i个文本（小说类型），value为第i个小说类型url
    return dict  # 返回字典，即存储各个类型的url（如玄幻小说：网址）


# 获取各类小说里的每一本小说url
def get_novel(indexUrl):
    dict = get_novelTypelUrl(indexUrl)  # 调用get_novelTypeUrl()传入首页url，返回各类型的url的字典
    novelTypeList = []
    for key, value in dict.items():  # 遍历字典内的key和value值，key值即字典
        html = open_url(value)  # 调用open_url()获取类型小说URL的内容，传入value
        soup = BeautifulSoup(html, "html.parser")  # 解析网页
        novelList = []
        typeDict = {}
        urlList = []
        for s2 in soup.find_all('div', attrs={'class': "l"})[0].select(
                '.s2'):  # 遍历查找div中class=l的第一个元素，在里面再选类名为s2的元素返回的列表
            novelList += s2.find_all('a')  # 从s2元素中选出a标签返回的列表拼接到novelList列表中
        for url in novelList:  # 遍历novelList列表中的a标签
            url = url['href']  # 获取a标签内的href属性重新赋值到url
            urlList.append(url)  # 将获取的小说url添加到列表中
        typeDict[key] = urlList  # key值为小说的类型，value为存取该类型的所有小说url的列表
        novelTypeList.append(typeDict)  # 将该字典添加到小说类型列表中
    return novelTypeList  # 返回该列表


# 进度条系统
def show(title, maxnum, lab):
    root3 = tk.Tk()  # 创建窗口
    root3.title('Yinkay的书荒拯救机')
    root3.geometry("800x450+400+200")  # 长x宽+x*y
    num = 0

    print(file_path + lab + '图片\\' + title + '.jpg')
    photo = ImageTk.PhotoImage(file=file_path + lab + '图片\\' + title + '.jpg')
    imageLabel = tk.Label(root3, image=photo)
    imageLabel.pack(padx=10, pady=10)

    label1 = tkinter.Label(root3, text="开始下载《" + title + "》:")
    label2 = tkinter.Label(root3, text="这部小说一共有" + str(maxnum) + "章")
    label1.pack()
    label2.pack()
    progressbarF = tkinter.ttk.Progressbar(root3)
    progressbarF.place(relx=0.1, rely=0.95, width=650, height=6)
    progressbarF['maximum'] = maxnum
    progressbarF['value'] = 0
    while num < maxnum:
        a = (count / maxnum) * 100  # 当前章节数/总章节数即下载的比例
        label3 = tkinter.Label(root3, text='正在下载第' + str(count) + '章,进度' + str(a) + '%')
        label3.pack()
        progressbarF['value'] += 1
        num += 1
        time.sleep(1)
        root3.update()
        label3.destroy()


# 获取小说信息
def get_novelInfo(self, indexUrl):
    global network_interval  # 需要操作函数外面的网络时间间隔变量
    novelUrlTypeList = get_novel(indexUrl)  # 存取各类的每一本小说的url
    global flage
    flage = 0
    novelTypeList = []
    novelTypeDict = {}

    # 小说爬取
    for dict in novelUrlTypeList:  # 遍历存取各类小说的列表，元素是字典（key是类型，value是该类型全部小说的列表）
        noveUrlList = []
        novelType = ''
        novelList = []
        for key, value in dict.items():  # 遍历一类小说的字典key，value，即例如存取所有玄幻小说的列表
            novelType = key
            noveUrlList = value
        path = '..\\程序执行结果文件\\' + str(
            novelType) + "图片"  # 写好一个路径
        isExists = os.path.exists(path)  # 判断该路径下有无文件夹novelType（小说类型名，用来存取名字）
        if not isExists:  # 如果不存在，创建该文件夹
            os.makedirs(path)
            print(novelType + '创建成功！')
        else:  # 存在，提示已经创建文件夹
            print(novelType + '已存在！')
        for novel in noveUrlList:  # 遍历存取该类型每一部小说的url
            novelDict = {}
            html = open_url(novel)  # 调用open_url（）返回url的内容
            soup = BeautifulSoup(html, 'html.parser')  # 解析网页内容
            title = str(soup.find_all('h1')[0].text)  # 查找h1标签的第一个元素的text，即是标题
            pList = soup.find_all('div', id="info")[0].select('p')  # 查找div，id为info的第一个元素，select其中的p元素，返回存取p元素的列表
            author = str(pList[0].text).replace('作    者：', '')  # p元素第一个的text即是作者，但是需要切割掉作者名字前的作者多余字符串
            updateTime = str(pList[2].text)  # plist的第三个元素的text即最新更新时间
            newChapter = str(pList[3].text)  # plist的第四个元素的text即最新章节
            pList = soup.find_all('div', id="intro")[0].select('p')  # 查找div，id为intro的第一个元素，查找其中的p元素返回列表
            novelIntro = str(pList[1].text)  # pList的第二个元素的text即是小说的简介
            imgList = soup.find_all('div', id='fmimg')[0].select('img')  # 查找div，id为fmimg的第一个元素，select出img元素返回列表
            imgUrl = str(imgList[0]['src'])  # 图片列表中的第一个img元素即是小说的简介图片，其中的src属性即是url
            imgContent = requests.get(imgUrl).content  # 获取图片链接的二进制内容
            writeImg(key, title, imgContent)  # 调用writeImg函数将图片写入本地文件夹
            novelDict['标题'] = title  # 小说的标题存取到novelDict字典的标题内
            novelDict['作者'] = author  # 小说的作者存取到novelDict字典的作者内
            novelDict['更新时间'] = updateTime  # 小说的更新时间存取到novelDict字典的更新时间内
            novelDict['最新章节'] = newChapter  # 小说的最新章节存取到novelDict字典的最新章节内
            novelDict['小说介绍'] = novelIntro  # 小说的介绍存取到novelDict字典的小说介绍内
            novelDict['链接'] = novel  # 小说的链接存取到novelDict字典的小说介绍内
            print('链接：', novel)
            novelList.append(novelDict)  # 将小说字典添加到分类小说列表
            time.sleep(network_interval)  # 每爬一本小说的信息暂停网络时间间隔秒，来模拟人访问网站
        novelTypeDict[novelType] = novelList  # 爬完一种类型的全部小说列表，添加到类型字典里面
        novelTypeList.append(novelTypeDict)  # 再把字典添加到列表中
        writeNovelData(novelType, novelList)  # 将爬取的一类型的全部小说保存到本地
    flage = 1
    return


# 查找小说并下载
def findtxt(lab):
    data = {
        'searchkey': lab
    }
    r = requests.post('https://www.xbiquge.la/modules/article/waps.php', params=data)
    r.encoding = 'utf-8'
    htmls = r.text
    soup = BeautifulSoup(htmls, "html.parser")
    novelList = []
    writer = []
    typeDict = {}
    writerdata = []
    for s2 in soup.find_all('tr'):  # 遍历查找div中class=l的第一个元素，在里面再选类名为s2的元素返回的列表
        novelList += s2.find_all('a')  # 从s2元素中选出a标签返回的列表拼接到novelList列表中
        writer += s2.find_all(attrs={'class': "even"})
    # print(novelList)
    # print(writer)
    flage = 1
    flage2 = 1
    for name in writer:
        pattern = re.compile('"even">(.*?)</td>')
        result2 = re.findall(pattern, str(name))
        if flage2 % 2 == 0:
            writerdata.append(str(result2))
        flage2 += 1
    n = 0
    for url in novelList:  # 遍历novelList列表中的a标签
        pattern = re.compile('">(.*?)</a>')
        result2 = re.findall(pattern, str(url))
        url = url['href']  # 获取a标签内的href属性重新赋值到url
        if not flage % 2 == 0:
            cou = [writerdata[n], str(url)]
            typeDict[str(result2)] = cou
            n += 1
        flage += 1
    root3 = tk.Tk()  # 创建窗口
    root3.title('Yinkay的书荒拯救机')
    root3.geometry("675x325+450+225")  # 长x宽+x*y
    root3.resizable(width=False, height=False)
    columns = ('小说名', '作者', '链接')

    if bool(typeDict):
        label3 = Label(root3, text='查找到' + str(len(typeDict)) + '本')
        label3.grid(row=0, column=0)
        # 创建一个 Treeview 小部件
        tree = ttk.Treeview(root3,
                            columns=columns,
                            show='headings')
        # 指定列的标题
        tree.heading('小说名', text='小说名')
        tree.heading('作者', text='作者')
        tree.heading('链接', text='链接')
        # 生成用于在 Treeview 上显示的元组列表
        bookname = []
        for key, value in typeDict.items():
            rownum = [key, value[0], value[1]]
            bookname.append(rownum)
        contacts = []
        print(bookname)
        for n in bookname:
            contacts.append((f'{n[0]}', f'{n[1]}', f'{n[2]}'))
        # 使用 Treeview 小部件的 insert() 方法一个一个地创建新项目
        for contact in contacts:
            tree.insert('', tk.END, values=contact)

        # 将 Treeview 小部件放在根窗口上
        tree.grid(row=1, column=0, sticky='nsew', columnspan=2, rowspan=2, ipadx=20, pady=20)
        # 向 Treeview 小部件添加垂直滚动条
        scrollbar = ttk.Scrollbar(root3, orient=tk.VERTICAL, command=tree.yview)
        tree.configure(yscroll=scrollbar.set)
        scrollbar.grid(row=1, column=3, sticky='ns', rowspan=2)

        def color():
            for selected_item in tree.selection():
                item = tree.item(selected_item)
                record = item['values']
                root3.destroy()
                print(record[2], lab)
                downloadNovel(record[2], lab)

        theButton = tk.Button(root3, text="下载", command=color)
        theButton.grid(row=4, column=0, columnspan=2, sticky='ew')
        # 启动
        root3.mainloop()
    else:
        messagebox.showinfo('提示', '查无此书')


# 保存小说图片
def writeImg(key, title, imgContent):  # imgContent传入的是小说url的content二进制形式
    with open('..\\程序执行结果文件\\' + key + '图片\\' + title + '.jpg',
              'wb') as f:  # wb方式打开，存取到对应的title.jpg里面
        f.write(imgContent)  # 将图片以二进制的方式写到本地


# 保存小说简介到csv
def writeNovelData(key, novelList):
    with open(key + '.csv', 'w', encoding='UTF-8', newline='') as f:  # 以w方式打开csv文件
        writer = csv.DictWriter(f, fieldnames=['标题', '作者', '更新时间', '最新章节', '小说介绍', '链接'])  # 创建一个csv对象，头为其中的标题，作者等
        writer.writeheader()  # 写入csv头
        for each in novelList:  # 循环小说列表
            writer.writerow(each)  # 将其每一本小说写入csv中


# 爬取小说每一章节的URL
def chapter_url(url):
    html = open_url(url)  # 返回url对应的content内容
    soup = BeautifulSoup(html, 'html.parser')  # 将本地的html文件转化为beautifulSoup对象，解析html
    charpterUrlList = []
    charpterurl = soup.find('div', id="list").select('a')  # 查找网页内的div标签，id为list的第一个元素，从中选出多个a标签返回列表
    for i in charpterurl:  # 遍历a标签列表
        i = i['href']  # a标签的href属性重新赋值给i
        trueUrl = 'http://www.xbiquge.la' + i  # 小说的url拼接上章节url（不完整）即可就是有效的章节url地址
        charpterUrlList.append(trueUrl)  # 将正确的章节url地址添加到列表中
    return charpterUrlList  # 返回一本小说的存储所有章节的url


# 获取章节内容
def get_content(url):
    pageHtml = open_url(url)  # 返回url对应的content内容
    soup = BeautifulSoup(pageHtml, 'html.parser')  # 解析返回的pagehtml网页
    chapterTitle = soup.h1.string  # 获取网页的h1标签，即小说的标题
    chapterContent = soup.find_all('div', id='content')[0].text  # 查找章节中div元素，id为content的一个元素的text
    chapterContent = ' '.join(chapterContent.split())  # 以空格为分隔符，分成列表，再组合成字符串
    content = chapterContent.replace(' ', '\r\n\n')  # 将空格替换成，\r\n\n，进行换行输出
    finallContent = chapterTitle + '\r\n\n\n' + content  # 对标题和文本进行连接，同时中间加入换行符
    return finallContent  # 返回最终组合的小说章节内容文本


# 下载小说
def downloadNovel(url, lab):
    global count
    pageHtml = open_url(url)  # 获取小说url里的内容content
    soup = BeautifulSoup(pageHtml, 'html.parser')  # 解析url的网页内容
    title = soup.h1.string  # 获取网页的h1标题字符串
    print("开始下载《" + title + "》:")
    chapterlist = chapter_url(url)  # 调用chapter_url()获取小说全部章节的url的列表
    lenchapter = len(chapterlist)  # 计算列表长度，即小说章节数
    print("这部小说一共有%d章" % lenchapter)

    root3 = tk.Tk()  # 创建窗口
    root3.title('Yinkay的书荒拯救机')
    root3.geometry("800x450+400+200")  # 长x宽+x*y

    my_file = Path(file_path + lab + '图片\\' + title + '.jpg')
    if my_file.is_file():
        photo = ImageTk.PhotoImage(file=file_path + lab + '图片\\' + title + '.jpg')
        imageLabel = tk.Label(root3, image=photo)
        imageLabel.pack(padx=10, pady=10)

    label1 = tkinter.Label(root3, text="开始下载《" + title + "》:")
    label2 = tkinter.Label(root3, text="这部小说一共有" + str(lenchapter) + "章")
    label1.pack()
    label2.pack()
    progressbarF = tkinter.ttk.Progressbar(root3)
    progressbarF.place(relx=0.1, rely=0.80, width=650, height=6)
    progressbarF['maximum'] = lenchapter
    progressbarF['value'] = 0

    count = 1  # 当前章节数
    for url in chapterlist:  # 遍历小说章节列表url
        text = get_content(url)  # 获取章节内容content
        with open('..\\程序执行结果文件\\小说\\' + title + '.txt', 'a',
                  encoding='UTF-8') as f:  # a方式追加到txt
            f.write(text + '\r\n\n\n\n')  # 每写入一章，就写入换行
        a = (count / lenchapter) * 100  # 当前章节数/总章节数即下载的比例

        label3 = tkinter.Label(root3, text='正在下载第' + str(count) + '章,进度' + str(a) + '%')
        label3.pack()
        progressbarF['value'] += 1
        time.sleep(1)
        root3.update()
        label3.destroy()

        print('正在下载%d章,进度%.2f%%' % (count, a))
        count += 1
        time.sleep(network_interval)  # 每爬一章停顿一下，避免503
    print("下载完成")
    root3.destroy()
    flage2 = 1
    return flage2


# 加载界面
def Draw(canvas, a, b, c, d):
    global flage
    global start1
    global start2
    global start3
    global start4
    while 1:
        if flage == 1:
            canvas.destroy()
            return
        a = canvas.create_arc(400, 400, 100, 100, start=start1, extent=30, fill="Silver")
        b = canvas.create_arc(400, 400, 100, 100, start=start2, extent=30, fill="Silver")
        c = canvas.create_arc(400, 400, 100, 100, start=start3, extent=30, fill="Silver")
        d = canvas.create_arc(400, 400, 100, 100, start=start4, extent=30, fill="Silver")
        time.sleep(0.2)
        canvas.delete('all')  # 删除上一个图形
        start1 += 20
        start2 += 20
        start3 += 20
        start4 += 20


def dealy():
    # 加载页面
    global flage
    global start1
    global start2
    global start3
    global start4
    # 初始图象
    root2 = tk.Tk()
    root2.geometry("800x500+400+200")
    root2.title('Yinkay的书荒拯救机')
    canvas = Canvas(root2, height=500, width=500)
    imageLabel = tk.Label(root2, text='爬取中...若小风车消失，即为爬取结束，请关闭页面！', pady=20)
    imageLabel.pack()
    canvas.pack()
    a = canvas.create_arc(400, 400, 100, 100, start=start1, extent=30, fill="Silver")
    b = canvas.create_arc(400, 400, 100, 100, start=start2, extent=30, fill="Silver")
    c = canvas.create_arc(400, 400, 100, 100, start=start3, extent=30, fill="Silver")
    d = canvas.create_arc(400, 400, 100, 100, start=start4, extent=30, fill="Silver")
    t = threading.Thread(target=Draw, args=(canvas, a, b, c, d))  # 开启线程
    t.start()
    root2.mainloop()
    t.join()
    if flage == 1:
        root2.destroy()
        return


def txt():
    global flage
    for i in range(1, 10):
        time.sleep(0.2)
        print(i)
    flage = 1
    return


def run(self, indexUrl):
    # 开启多线程池
    threadpool = ThreadPoolExecutor(2)
    threadpool.submit(dealy)
    t = threading.Thread(target=get_novelInfo,args=(self, indexUrl))  # 开启线程
    t.start()
    t.join()
    threadpool.shutdown(True)  # 等待线程池中的任务执行完毕后，在继续执行
    return flage
